#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <windows.h>
#include <conio.h>
#define HEIGHT 29
#define WIDTH 110 

const int dir[4][2] = {         //数组第一个坐标为y坐标
    {-1,0},         //向上
    {0,1},          //向右
    {1,0},          //向下
    {0,-1}          //向左
};

typedef enum BlockType {
    EMPTY = 0,
    FOOD = 1,
    BOUND = 2,
}BlockType;

typedef enum Direction {
    UP = 0,
    RIGHT = 1,
    DOWN = 2,
    LEFT = 3,
}Direction;

typedef struct Map
{
    BlockType data[HEIGHT][WIDTH];
    bool hasFood;
}Map;

typedef struct Pos {
    int x;
    int y;
}Pos;

typedef struct Snake {
    Pos snake[HEIGHT * WIDTH];
    int snakeLength;
    Direction snakeDirection;
    int lastMoveTime;
    int moveFrequency;
}Snake;
/*系统函数 */
void hideCursor();
void hideCursor() {
    HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
    CONSOLE_CURSOR_INFO curInfo = { 1,FALSE };
    SetConsoleCursorInfo(hOutput, &curInfo);
}
void drawUnit(Pos p,char unit[]);
void drawUnitXY(int x,int y,char unit[]);
void drawIntXY(int x,int y,int unit);
void drawXDU();
void drawUnit(Pos p,char unit[]){
    COORD coord;
    HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
    coord.X = p.x + 1;      //绘制地图时有一圈边框，所以要加上偏移量1
    coord.Y = p.y + 1;
    SetConsoleCursorPosition(hOutput,coord);
    printf("%s",unit);
}

void drawUnitXY(int x,int y,char unit[]){
    COORD coord;
    HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
    coord.X = x + 1;      //绘制地图时有一圈边框，所以要加上偏移量1
    coord.Y = y + 1;
    SetConsoleCursorPosition(hOutput,coord);
    printf("%s",unit);
}

void drawIntXY(int x,int y,int unit){
    COORD coord;
    HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
    coord.X = x + 1;      //绘制地图时有一圈边框，所以要加上偏移量1
    coord.Y = y + 1;
    SetConsoleCursorPosition(hOutput,coord);
    printf("%d",unit);
}
void drawXDU(){
    drawUnitXY(4,1,"█");
    drawUnitXY(8,1,"█");
    drawUnitXY(5,2,"█");
    drawUnitXY(7,2,"█");
    drawUnitXY(6,3,"█");
    drawUnitXY(5,4,"█");
    drawUnitXY(7,4,"█");
    drawUnitXY(4,5,"█");
    drawUnitXY(8,5,"█");
    drawUnitXY(10,1,"█");
    drawUnitXY(11,1,"█");
    drawUnitXY(12,1,"█");
    drawUnitXY(10,2,"█");
    drawUnitXY(13,2,"█");
    drawUnitXY(10,3,"█");
    drawUnitXY(14,3,"█");
    drawUnitXY(10,4,"█");
    drawUnitXY(13,4,"█");
    drawUnitXY(10,5,"█");
    drawUnitXY(11,5,"█");
    drawUnitXY(12,5,"█");
    drawUnitXY(16,1,"█");
    drawUnitXY(20,1,"█");
    drawUnitXY(16,2,"█");
    drawUnitXY(20,2,"█");
    drawUnitXY(16,3,"█");
    drawUnitXY(20,3,"█");
    drawUnitXY(16,4,"█");
    drawUnitXY(20,4,"█");
    drawUnitXY(17,5,"█");
    drawUnitXY(18,5,"█");
    drawUnitXY(19,5,"█");
    drawUnitXY(4,7,"MADE BY:");
    drawUnitXY(4,8,"ZIHAN WANG");
    drawUnitXY(4,9,"JUNTONG ZHAO");
}
/*地图及障碍物绘制函数*/
void initMap(Map* map);
void initBound(Map* map);
void drawMap(Map* map);
void initMap(Map* map){
    int x,y;
    for(y = 0;y < HEIGHT; y++){
        for(x = 0;x < WIDTH; x++){
            map->data[y][x] = EMPTY;
        }
    }
    map->hasFood = false;
}

void initBound(Map* map){
    int x,y;
    for(y = 20;y < 21; y++){
        for(x = 50;x < 56; x++){
            map->data[y][x] = BOUND;
        }
    }
    for(y = 12;y < 13; y++){
        for(x = 43;x < 47; x++){
            map->data[y][x] = BOUND;
        }
    }
    for(x = 43;x < 44;x++){
        for(y = 12;y < 17;y++){
            map->data[y][x] = BOUND;
        }
    }
    for(y = 5;y < 10; y++){
        for(x = 11;x < 12; x++){
            map->data[y][x] = BOUND;
        }
    }
}

void drawMap(Map* map){
    int i,j;
    system("cls");
    printf("╭");
    for(i = 0;i<WIDTH;i++){
        printf("─");
    }
    printf("╮\n");
    for(i = 0;i < HEIGHT;i++){
        printf("│"); 
        for(j = 0;j < WIDTH;j++){
            if(map->data[i][j] == EMPTY) printf(" ");
            else if(map->data[i][j] == BOUND) printf("█");
        }
        printf("│\n");
    }
    printf("╰");
    for(i = 0;i < WIDTH;i++){
        printf("─");
    }
    printf("╯");
}
/*蛇体绘制函数*/
void initSnake(Snake* snake);
void drawSnake(Snake* snk);
void initSnake(Snake* snake){
    snake->snakeLength = 1;
    snake->snakeDirection = UP;
    snake->snake[0].x = WIDTH/2;
    snake->snake[0].y = HEIGHT/2;
    snake->lastMoveTime = 0;
    snake->moveFrequency = 70;
}

void drawSnake(Snake* snk){
    int i;
    for(i = 0;i < snk->snakeLength;i++){
        drawUnit(snk->snake[i],"●");
    }
}
/*游戏逻辑函数*/
void checkEatFood(Snake* snk,Pos tail,Map* map);
bool checkOutOfBound(Pos p,Map* map);
void moveSnake(Snake *snk);
bool doMove(Snake *snk,Map *map);
bool checkSnakeMove(Snake *snk,Map *map);
bool checkChangeDir(Snake *snk);
void checkFood(Map *map,Snake *snk);

void checkEatFood(Snake* snk,Pos tail,Map* map){
    Pos head = snk->snake[0];
    if(map->data[head.y][head.x] == FOOD){
        snk->snake[snk->snakeLength++] = tail;    //吃到食物，加长蛇身  
        map->data[head.y][head.x] = EMPTY;
        map->hasFood = false;
        drawUnit(tail,"●");
    }
}

bool checkOutOfBound(Pos p,Map* map){
    if(p.x == 0 || p.x ==WIDTH + 1){
        return true;
    }
    if(p.y == 0 || p.y == HEIGHT + 1){
        return true;
    }
    if(map->data[p.y][p.x] == BOUND){
        return true;
    }
    return false;
}


void moveSnake(Snake *snk){
    int i;
    for(i = snk->snakeLength-1;i > 0;i--){
        snk->snake[i] = snk->snake[i-1];
    }
    snk->snake[0].y += dir[snk->snakeDirection][0];
    snk->snake[0].x += dir[snk->snakeDirection][1];
}

bool doMove(Snake *snk,Map *map){
    int i;
    Pos tail = snk->snake[snk->snakeLength-1];
    drawUnit(tail," ");
    moveSnake(snk);
    if(checkOutOfBound(snk->snake[0],map)){
        return false; 
    }
    for (i = 1; i < snk->snakeLength; i++) {
        if (snk->snake[0].x == snk->snake[i].x && 
            snk->snake[0].y == snk->snake[i].y) {
            return false; // 撞到蛇身，游戏结束
        }
    }
    checkEatFood(snk,tail,map);
    drawUnit(snk->snake[0],"●");
    return true;
}

bool checkSnakeMove(Snake *snk,Map *map){
    int curTime = GetTickCount();
    if(curTime - snk->lastMoveTime > snk->moveFrequency){
        if(doMove(snk,map) == false){
            return false;
        }
        snk->lastMoveTime = curTime;
    }
    return true;
}

bool checkChangeDir(Snake *snk){
    if(_kbhit()){        //keybord hit
        switch(_getch()){
            case 'w':
                if(snk->snakeDirection!= DOWN){
                    snk->snakeDirection = UP;
                }
                break;
            case 'a':
                if(snk->snakeDirection != RIGHT){
                    snk->snakeDirection = LEFT;
                }
                break;
            case 'd':
                if(snk->snakeDirection != LEFT){
                    snk->snakeDirection = RIGHT;
                }
                break;
            case 's':
                if(snk->snakeDirection != UP){
                    snk->snakeDirection = DOWN;
                }
                break;
            case 27:
                return false;
            default:
                break;
        }
    }
    return true;
}

void checkFood(Map *map,Snake *snk){
    if(map->hasFood == false){
        while(true){
            int x = rand() % WIDTH;
            int y = rand() % HEIGHT;
            int i = 0;
            while(i < snk->snakeLength){
                if(x == snk->snake[i].x && y == snk->snake[i].y && map->data[y][x] == BOUND){
                    break;
                }
                i++;
            }
            if(i == snk->snakeLength){
                map->data[y][x] = FOOD;
                map->hasFood = true;
                Pos foodPos;
                foodPos.x = x;
                foodPos.y = y;
                drawUnit(foodPos,"◆");
                return;
            }
        }
    }
}

int readHighScore(){
    FILE* fp = fopen("score.txt", "r");
    int score = 0;
    if(fp) {
        fscanf(fp, "%d", &score);
        fclose(fp);
    }
    return score;
}

void saveHighScore(int newScore){
    FILE* fp = fopen("score.txt", "w");
    if(fp) {
        fprintf(fp, "%d", newScore);
        fclose(fp);
    }
}

